(function () {
  const inBrowser = typeof window !== "undefined";
  const supportTouch = inBrowser && "ontouchstart" in window;
  let initiated = false;
  let eventTarget;

  const isMousedown = (eventType) => eventType === "mousedown";

  const isMousemove = (eventType) => eventType === "mousemove";

  const isMouseup = (eventType) => eventType === "mouseup";

  const isUpdateTarget = (eventType) =>
    isMousedown(eventType) ||
    !eventTarget ||
    (eventTarget && !eventTarget.dispatchEvent);

  function Touch(target, identifier, mouseEvent) {
    const { clientX, clientY, screenX, screenY, pageX, pageY } = mouseEvent;

    this.identifier = identifier;
    this.target = target;
    this.clientX = clientX;
    this.clientY = clientY;
    this.screenX = screenX;
    this.screenY = screenY;
    this.pageX = pageX;
    this.pageY = pageY;
  }

  function createTouchList() {
    const touchList = [];

    touchList.item = function (index) {
      return this[index] || null;
    };

    touchList.identifiedTouch = function (id) {
      return this[id + 1] || null;
    };

    return touchList;
  }

  function createTouchListWithEvent(mouseEvent) {
    const touchList = createTouchList();
    touchList.push(new Touch(eventTarget, 1, mouseEvent));
    return touchList;
  }

  function getActiveTouches(mouseEvent) {
    const { type } = mouseEvent;

    if (isMouseup(type)) {
      return createTouchList();
    }

    return createTouchListWithEvent(mouseEvent);
  }

  function triggerTouch(touchType, mouseEvent) {
    const { altKey, ctrlKey, metaKey, shiftKey } = mouseEvent;
    const touchEvent = new Event(touchType, {
      bubbles: true,
      cancelable: true,
    });

    touchEvent.altKey = altKey;
    touchEvent.ctrlKey = ctrlKey;
    touchEvent.metaKey = metaKey;
    touchEvent.shiftKey = shiftKey;

    touchEvent.touches = getActiveTouches(mouseEvent);
    touchEvent.targetTouches = getActiveTouches(mouseEvent);
    touchEvent.changedTouches = createTouchListWithEvent(mouseEvent);

    eventTarget.dispatchEvent(touchEvent);
  }

  function onMouse(mouseEvent, touchType) {
    const { type, target } = mouseEvent;

    initiated = isMousedown(type) ? true : isMouseup(type) ? false : initiated;

    if (isMousemove(type) && !initiated) return;

    if (isUpdateTarget(type)) eventTarget = target;

    triggerTouch(touchType, mouseEvent);

    if (isMouseup(type)) eventTarget = null;
  }

  function createTouchEmulator() {
    window.addEventListener(
      "mousedown",
      (event) => onMouse(event, "touchstart"),
      true,
    );
    window.addEventListener(
      "mousemove",
      (event) => onMouse(event, "touchmove"),
      true,
    );
    window.addEventListener(
      "mouseup",
      (event) => onMouse(event, "touchend"),
      true,
    );
  }

  if (inBrowser && !supportTouch) {
    createTouchEmulator();
  }
})();
